package mk.jug.academy.service;

import mk.jug.academy.exception.BusinessException;
import mk.jug.academy.model.Course;
import mk.jug.academy.model.Lecturer;
import mk.jug.academy.model.Page;
import mk.jug.academy.model.status.LecturerStatus;
import mk.jug.academy.preconditions.ErrorCodes;
import mk.jug.academy.preconditions.PreconditionExceptions;
import mk.jug.academy.testing.bdd.IntegrationBehavior;
import mk.jug.academy.testing.factory.CourseTestFactory;
import mk.jug.academy.testing.factory.LecturerTestFactory;
import mk.jug.academy.testing.util.TestHelper;

import org.springframework.beans.factory.annotation.Autowired;

import com.googlecode.instinct.marker.annotate.Specification;
import com.googlecode.instinct.marker.annotate.Specification.SpecificationState;
import com.googlecode.instinct.marker.annotate.Subject;

/**
 * An integration test unit covering the behavior exhibited regarding the
 * {@link Lecturer} management process.
 *
 * @author Ice.Penov
 */
public class LecturerManagement extends IntegrationBehavior {
	
	@Autowired private CourseService courseService;
	@Autowired private LecturerService lecturerService;
	
	@Subject private Course course1;
	@Subject private Course course2;
	
	@Subject private Lecturer lecturer1;
	@Subject private Lecturer lecturer2;
	@Subject private Lecturer lecturer3;
	
	public void beforeHook() {
		TestHelper.init(courseService);
		TestHelper.init(lecturerService);
		
		course1 = CourseTestFactory.createCourse();
		course2 = CourseTestFactory.createCourse();
		
		lecturer1 = LecturerTestFactory.createLecturer();
		lecturer2 = LecturerTestFactory.createLecturer();
		lecturer3 = LecturerTestFactory.createLecturer();
	}
		
	/**
	 * User action:
	 * An {@link Lecturer} is being assigned to a {@link Course} not in the AVAILABLE state.
	 * 
	 * Expected behavior:
	 * 1. Throw an {@link IllegalStateException} with an appropriate message.
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void failToAssignLecturerIfCourseIsNotInAvailableState() {
		courseService.submitNewCourse(course1);
		lecturerService.registerLecturer(lecturer1);
		//assign to a hatching course.
		new ExceptionExpectation() {
			public void execute() {
				courseService.assignLecturer(course1, lecturer1);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_COURSE_LECTURER_ASSIGN);
		courseService.hatchCourse(course1);
		courseService.startCourse(course1);
		//assign to an already running course.
		new ExceptionExpectation() {
			public void execute() {
				courseService.assignLecturer(course1, lecturer1);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_COURSE_LECTURER_ASSIGN);
		courseService.finishCourse(course1);
		courseService.deactivateCourse(course1);
		//assign to a deactivated course.
		new ExceptionExpectation() {
			public void execute() {
				courseService.assignLecturer(course1, lecturer1);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_COURSE_LECTURER_ASSIGN);
	}
	
	/**
	 * User action:
	 * A non ACTIVE {@link Lecturer} is being assigned to a {@link Course}.
	 * 
	 * Expected behavior:
	 * 1. Throw an {@link IllegalStateException} with the appropriate message
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void failToAssignLecturerToCourseIfLecturerIsNotActive() {
		courseService.submitNewCourse(course1);
		courseService.hatchCourse(course1);
		lecturerService.registerLecturer(lecturer1);
		//assign SIGNED_UP lecturer
		new ExceptionExpectation() {
			public void execute() {
				courseService.assignLecturer(course1, lecturer1);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_LECTURER_ACTIVE_STATE);
		lecturerService.acceptRegistration(lecturer1);
		lecturerService.retireLecturer(lecturer1);
		//assign RETIRED lecturer
		new ExceptionExpectation() {
			public void execute() {
				courseService.assignLecturer(course1, lecturer1);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_LECTURER_ACTIVE_STATE);
		lecturer1.setStatus(LecturerStatus.REJECTED);
		//asign REJECTED lecturer
		new ExceptionExpectation() {
			public void execute() {
				courseService.assignLecturer(course1, lecturer1);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_LECTURER_ACTIVE_STATE);
	}
	
	/**
	 * User action:
	 * Assign a non-persisted {@link Lecturer} to an existing {@link Course}. 
	 * 
	 * Expected behavior:
	 * 1. Throw an {@link IllegalArgumentException} with the appropriate message.
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void failToAssignLecturerToCourseIfLecturerIsNotSaved() {
		courseService.submitNewCourse(course1);
		courseService.hatchCourse(course1);
		new ExceptionExpectation() {
			public void execute() {
				courseService.assignLecturer(course1, lecturer1);
			}
		}.checkFor(IllegalArgumentException.class, PreconditionExceptions.ID_IS_NULL);
	}
	
	/**
	 * User action:
	 * Assign a {@link Lecturer} to a {@link Course}.
	 * 
	 * Expected behavior:
	 * 1. The {@link Course} should list that {@link Lecturer}
	 * 2. The {@link Lecturer} should list this {@link Course}
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void assignLecturerToCourse() {
		//initialize.
		TestHelper.prepareAvailableCourse(course1, course2);
		TestHelper.prepareActiveLecturer(lecturer1, lecturer2, lecturer3);
		//make the calls.
		courseService.assignLecturer(course1, lecturer1);
		expect.that(course1.getLecturerID()).isEqualTo(lecturer1.getId());
		expect.that(course1.getLecturerName()).isEqualTo(lecturer1.getDisplayName());
		//also:		
		expect.that(lecturerService.fetchCoursesForLecturer(lecturer1)).isOfSize(1);
		expect.that(lecturerService.fetchCoursesForLecturer(lecturer1)).containsItem(course1);
	}
	
	/**
	 * User action:
	 * Remove a {@link Lecturer} from a {@link Course}
	 * 
	 * Expected behavior:
	 * 1. The {@link Course} should no longer list that {@link Lecturer}
	 * 2. The {@link Lecturer} should no longer list that {@link Course}
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void removeLecturerFromCourse() {
		//initialize.
		TestHelper.prepareAvailableCourse(course1, course2);
		TestHelper.prepareActiveLecturer(lecturer1, lecturer2, lecturer3);
		//make the assignments.
		courseService.assignLecturer(course1, lecturer1);
		//now, remove it.
		courseService.removeLecturer(course1, lecturer1);
		//expectations:
		expect.that(course1.getLecturerID()).isNull();
		expect.that(course1.getLecturerName()).isNull();
		//also:
		expect.that(lecturerService.fetchCoursesForLecturer(lecturer1)).isEmpty();
	}
	
	/**
	 * User action:
	 * Edit the {@link Lecturer}s properties.
	 * 
	 * Expected behavior:
	 * 1. All changes to be saved.
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void editLecturerProperties() {
		TestHelper.prepareActiveLecturer(lecturer1);
		lecturer1.setBio("Changed");
		lecturer1.setFirstName("Changed");
		lecturer1.setLastName("Changed");
		lecturer1.setEmail("changed@test.com");
		lecturerService.saveChanges(lecturer1);
		Lecturer loaded = lecturerService.load(lecturer1.getId());
		expect.that(loaded.getBio()).isEqualTo(lecturer1.getBio());
		expect.that(loaded.getFirstName()).isEqualTo(lecturer1.getFirstName());
		expect.that(loaded.getLastName()).isEqualTo(lecturer1.getLastName());
		expect.that(loaded.getEmail()).isEqualTo(lecturer1.getEmail());
	}
	
	/**
	 * User action:
	 * Fetch the lecturers in a paged manner.
	 * 
	 * Expected behavior:
	 * List the records as expected.
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void navigateLecturersUsingPaging() {
		lecturer1.setRating(1);
		lecturer2.setRating(2);
		lecturer3.setRating(3);
		TestHelper.prepareActiveLecturer(lecturer1, lecturer2, lecturer3);
		//open the pages.
		Page<Lecturer> page1 = lecturerService.listActive(1, 2);
		Page<Lecturer> page2 = lecturerService.listActive(2, 2);
		Page<Lecturer> page3 = lecturerService.listActive(3, 2);
		//expectations.
		//size:
		expect.that(page1.getTotalCount()).isEqualTo(3);
		expect.that(page2.getTotalCount()).isEqualTo(3);
		expect.that(page3.getTotalCount()).isEqualTo(3);
		expect.that(page1.getItems()).isOfSize(2);
		expect.that(page2.getItems()).isOfSize(1);
		expect.that(page3.getItems()).isEmpty();
		//check contents of the pages:
		expect.that(page1.getItems()).containsItems(lecturer3, lecturer2);
		expect.that(page2.getItems()).containsItem(lecturer1);
	}
	
	/**
	 * User action:
	 * Register a lecturer in the system with a specific email.
	 * 
	 * Expected behavior:
	 * The system will return true, indicating there is such registered email. 
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void returnTrueIfEmailAlreadyInTheSystem() {
		lecturer1.setEmail("ipenov@gmail.com");
		lecturerService.registerLecturer(lecturer1);
		lecturerService.acceptRegistration(lecturer1);
		//fetch the lecturer by email.
		Lecturer fetched = lecturerService.findByEmail("ipenov@gmail.com");		
		//return null if non-existent.
		Lecturer shouldBeNull = lecturerService.findByEmail("non@existent.com");		
		expect.that(fetched).isEqualTo(lecturer1);
		expect.that(shouldBeNull).isNull();
		expect.that(lecturerService.exists("ipenov@gmail.com")).isTrue();
		expect.that(lecturerService.exists("non@existent.com")).isFalse();
	}
	
	/**
	 * User action:
	 * Register multiple lecturers in the system.
	 * 
	 * Expected behavior:
	 * They should be retrieved in the system in a paged manner,
	 * with the latest one (by time criteria) returned first.
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void listLatestLecturers() {
		lecturerService.registerLecturer(lecturer1);
		lecturerService.registerLecturer(lecturer2);
		//expectations.
		Page<Lecturer> page = lecturerService.listLatestByStatus(1, 2, LecturerStatus.SIGNED_UP);		
		expect.that(page.getItems()).isOfSize(2);
		expect.that(page.getItems()).containsItems(lecturer2, lecturer1);
		expect.that(page.getItems().get(0)).isEqualTo(lecturer2);
		expect.that(page.getItems().get(1)).isEqualTo(lecturer1);
	}
			
}